#pragma once

#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>
#include <google/protobuf/util/json_util.h>
#include <bsoncxx/builder/stream/document.hpp>
#include <bsoncxx/json.hpp>
#include <iostream>
#include <memory>
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <string>
#include <vector>

using namespace google::protobuf;

class MongoSystem {
public:
    MongoSystem();
    ~MongoSystem();

    int32_t Init(const std::string& url);

public:
    // Insert operations
    int32_t InsertOne(const std::string& collectionName, bsoncxx::v_noabi::document::view_or_value document,
                      mongocxx::options::insert const& options = {});
    int32_t InsertMany(const std::string& collectionName, const std::vector<bsoncxx::document::value>& documents);

    // Find operations
    int32_t FindOne(const std::string& collectionName, const bsoncxx::document::view_or_value& filter = {},
                    bsoncxx::document::value* result = nullptr);
    int32_t Find(const std::string& collectionName, const bsoncxx::document::view_or_value& filter = {},
                 const mongocxx::options::find& options = {}, std::vector<bsoncxx::document::value>* results = nullptr);

    // Update operations
    int32_t UpdateOne(const std::string& collectionName, const bsoncxx::document::view_or_value& filter,
                      const bsoncxx::document::view_or_value& update);
    int32_t UpdateMany(const std::string& collectionName, const bsoncxx::document::view_or_value& filter,
                       const bsoncxx::document::view_or_value& update);

    // Delete operations
    int32_t DeleteMany(const std::string& collectionName, const bsoncxx::document::view_or_value& filter);
    int32_t DeleteOne(const std::string& collectionName, const bsoncxx::document::view_or_value& filter);

    // Replace operation
    int32_t ReplaceOne(const std::string& collectionName, const bsoncxx::document::view_or_value& filter,
                       const bsoncxx::document::view_or_value& replacement);

    // Save protobuf message to MongoDB (uses InsertOne internally)
    template <typename T>
    bool save(const T& message, const std::string& collection_name) {
        static_assert(std::is_base_of<Message, T>::value, "T must be a protobuf Message type");

        try {
            // Protobuf 转 JSON
            std::string json_str;
            util::JsonPrintOptions options;
            options.add_whitespace = true;
            options.preserve_proto_field_names = true;
            options.always_print_primitive_fields = true;

            auto status = util::MessageToJsonString(message, &json_str, options);
            if (!status.ok()) {
                std::cerr << "Protobuf to JSON failed: " << status.message() << std::endl;
                return false;
            }

            // 添加元数据
            auto doc = bsoncxx::from_json(json_str);
            auto builder = bsoncxx::builder::stream::document{};

            // 复制原始字段
            for (auto&& element : doc.view()) {
                builder << element.key() << element.get_value();
            }

            // 添加元数据
            builder << "_proto_type" << message.GetTypeName() << "_created_at"
                    << bsoncxx::types::b_date{std::chrono::system_clock::now()};

            // Use InsertOne interface
            int32_t ret = InsertOne(collection_name, builder.view());
            return ret == 0;

        } catch (const std::exception& e) {
            std::cerr << "Error in save: " << e.what() << std::endl;
            return false;
        }
    }

    // Load protobuf message from MongoDB (uses FindOne internally)
    template <typename T>
    bool load(const std::string& collection_name, const bsoncxx::document::view_or_value& filter, T& message) {
        static_assert(std::is_base_of<Message, T>::value, "T must be a protobuf Message type");

        try {
            // Use FindOne interface
            bsoncxx::document::value result_doc(bsoncxx::builder::stream::document{}
                                                << bsoncxx::builder::stream::finalize);
            int32_t ret = FindOne(collection_name, filter, &result_doc);

            if (ret != 0) {
                return false;
            }

            // BSON 转 JSON
            std::string json_str = bsoncxx::to_json(result_doc.view());

            // JSON 转 Protobuf
            auto status = util::JsonStringToMessage(json_str, &message);

            return status.ok();

        } catch (const std::exception& e) {
            std::cerr << "Error in load: " << e.what() << std::endl;
            return false;
        }
    }

    // Load multiple protobuf messages from MongoDB (uses Find internally)
    template <typename T>
    std::vector<T> loadMany(const std::string& collection_name, const bsoncxx::document::view_or_value& filter,
                            int limit = 0) {
        static_assert(std::is_base_of<Message, T>::value, "T must be a protobuf Message type");

        std::vector<T> results;

        try {
            // Use Find interface
            mongocxx::options::find opts{};
            if (limit > 0) {
                opts.limit(limit);
            }

            std::vector<bsoncxx::document::value> doc_results;
            int32_t count = Find(collection_name, filter, opts, &doc_results);

            if (count > 0) {
                for (auto&& doc : doc_results) {
                    T message;
                    std::string json_str = bsoncxx::to_json(doc.view());
                    auto status = util::JsonStringToMessage(json_str, &message);

                    if (status.ok()) {
                        results.push_back(std::move(message));
                    }
                }
            }

        } catch (const std::exception& e) {
            std::cerr << "Error in loadMany: " << e.what() << std::endl;
        }

        return results;
    }

    // 获取 Protobuf 结构的可视化表示
    template <typename T>
    std::string getStructureVisualization() {
        static_assert(std::is_base_of<Message, T>::value, "T must be a protobuf Message type");

        T message;  // 创建默认实例
        return getMessageStructure(&message, 0);
    }

    // 打印 MongoDB 文档结构预览
    template <typename T>
    void printMongoStructure(const T& message) {
        static_assert(std::is_base_of<Message, T>::value, "T must be a protobuf Message type");

        std::cout << "\n=== MongoDB Document Structure ===" << std::endl;
        std::cout << "Collection: " << message.GetTypeName() << std::endl;
        std::cout << "\nDocument Fields:" << std::endl;

        std::string structure = getMessageStructure(&message, 0);
        std::cout << structure << std::endl;

        std::cout << "\nMongoDB JSON Preview:" << std::endl;
        std::string json_str;
        util::JsonPrintOptions options;
        options.add_whitespace = true;
        options.preserve_proto_field_names = true;
        options.always_print_primitive_fields = true;

        util::MessageToJsonString(message, &json_str, options);
        std::cout << json_str << std::endl;
        std::cout << "==================================\n" << std::endl;
    }

    // Update protobuf message in MongoDB (uses UpdateOne internally)
    template <typename T>
    bool update(const std::string& collection_name, const bsoncxx::document::view_or_value& filter, const T& message) {
        static_assert(std::is_base_of<Message, T>::value, "T must be a protobuf Message type");

        try {
            std::string json_str;
            util::JsonPrintOptions options;
            options.preserve_proto_field_names = true;

            auto status = util::MessageToJsonString(message, &json_str, options);
            if (!status.ok()) {
                return false;
            }

            auto doc = bsoncxx::from_json(json_str);
            auto update_doc = bsoncxx::builder::stream::document{}
                              << "$set" << doc << "$currentDate" << bsoncxx::builder::stream::open_document
                              << "_updated_at" << true << bsoncxx::builder::stream::close_document
                              << bsoncxx::builder::stream::finalize;

            // Use UpdateOne interface
            int32_t ret = UpdateOne(collection_name, filter, update_doc.view());
            return ret > 0;

        } catch (const std::exception& e) {
            std::cerr << "Error in update: " << e.what() << std::endl;
            return false;
        }
    }

    // Delete protobuf message from MongoDB (uses DeleteOne internally)
    template <typename T>
    bool remove(const std::string& collection_name, const bsoncxx::document::view_or_value& filter) {
        try {
            // Use DeleteOne interface
            int32_t ret = DeleteOne(collection_name, filter);
            return ret > 0;

        } catch (const std::exception& e) {
            std::cerr << "Error in remove: " << e.what() << std::endl;
            return false;
        }
    }

private:
    // 递归获取消息结构
    std::string getMessageStructure(const Message* message, int indent_level);

    // 获取字段类型字符串
    std::string getFieldTypeString(const FieldDescriptor* field);

private:
    mongocxx::client client;
    bool isInit = false;
};
